【老万】从0开始学chatGPT(十):相亲的数学
本文是《从0开始学chatGPT》系列第十篇,欢迎按需阅读:
上一次我们粗略了解了 chatGPT 中 transformer 的基本原理,这次我们来深入学习一下它的实现细节。
~~自注意力像相亲~~
我们知道,chatGPT 超强的语言能力很大程度上归功于 transformer 架构的自注意力(self-attention)机制。通过大量训练,GPT 学会了在处理输入记号(tokens)时为每个记号找到它关注的记号。准确地说,GPT 会计算出每个记号 Ti 对任意一个记号 Tj 的关注度,再根据这个 n×n 的注意力矩阵(attention matrix),通过加权平均的办法算出每个记号关注的嵌入向量(embedding vector)。这个嵌入向量就代表了记号在当前语境下的真实含义。
如果上面的描述太抽象,用光棍朋友们乐此不疲的相亲活动一比喻就好理解了:GPT 好比人民公园的相亲角,每个记号都是参加活动的嘉宾。进相亲角的时候,嘉宾们都得把个人条件写成一段短小精干的自我介绍,同时还得准备一份言简意赅的择偶要求。每位嘉宾会把大家的介绍跟自己的要求一一对比,按匹配程度打一个分,就得到了代表每对嘉宾之间关注度的注意力矩阵。
比如参加相亲的有小王、小张、小江三人,情况如下:
小王:体健貌端男,求貌美如花女。
小张:事业成功男,求温柔贤惠女。
小江:美艳泼辣女,求英俊潇洒男。
根据这些资料,我们可以计算出一个 3×3 的注意力矩阵(表中数值仅为示例用):
看得出来,小王中意小江(关注度高达 6.23),小张觉得小江只能说凑合(3.27),而小江最心仪的是小王(6.85),不过小张做个备胎也还行(3.53)。
注意力矩阵是怎么算出来的?它又如何帮助 AI 理解人类的语言?今天我们就来掰扯一下自注意力机制的实现细节,让你深度理解其原理。
在继续之前,强烈建议大家先复习《(九)有一种 AI 叫关注》,因为本文假设读者已经掌握了前文的内容。
~~线性投影有妙用~~
上次讲过,transformer 会通过三个经学习得来的线性投影运算把一个记号的高维输入向量变换成它的键向量 K(key vector,相当于个人简介)、查询向量 Q(query vector,相当于择偶条件)和值向量 V(value vector,相当于个人完整情况)。投影的目的是化繁为简、降低维数。这不但减少了计算量,还保证我们在计算记号关注度的时候不被次要因素带偏。
代表一个记号的输入向量维数很高(根据模型不同,可以从几百维到几千维不等),信息量太大。为了抓住重点,我们需要把不重要的信息去掉。但通常我们不能直接抹掉一部分维度,而是需要对向量进行一系列的旋转和缩放操作,再对部分坐标轴做投影,才能保证结果包含我们想要的那些信息。
为了帮助大家理解,我们用图形演示几个简单的例子。
假设在水浒的世界里,大伙儿择偶时会考虑颜值和人品这两个维度,也就是说他们会把每个潜在的对象用一个二维向量表示。
我们按这个原则来考察一下 Sunnyvale (阳谷县)三大知名男士。
首先是有个网红老婆的武大郎。武植被讥为三寸丁谷树皮,颜值自然是负分。但他为人忠厚,卖炊饼从不短斤少两,还有郓哥这样死心塌地的朋友,人品没大毛病,只是性格懦弱要减一些分。
他的弟弟二郎武松是远近闻名的顶流,身躯凛凛,相貌堂堂,一双眼光射寒星,两弯眉浑如刷漆,仗着酒劲猎杀过国家一级保护动物。人品颜值都顶呱呱。
阳谷县首富西门大官人大家也都知道。他色胆包天勾引有夫之妇,还怂恿小潘谋杀亲夫。但西门庆模样也是没得说,妥妥的高富帅。
如果将此三巨头按颜值和人品表示成二维向量,大概是这样的:
维度多了不好排序。二者不可兼得的时候,需要根据自己的价值观做一个权衡。用数学的语言讲,就是将二维向量投射到某个一维空间,变成一维向量(也就是一个实数),然后就可以排序了。
按照正统的官方观点,我们应该追求内在美而忽略对象的外在条件。在这样的柳下惠坐标系里。我们需要做的是一个对人品轴的投影,结果是武松 > 大郎 > 西门。
而在花痴协会的坐标系里,人品被完全忽略,只看颜值。也就是说改成对颜值轴做一个投影,武松 > 西门 > 大郎。
当然大多数情况下,人们的优先级不是那么泾渭分明。
比如潘金莲。
显然小潘对自己的婚姻生活深度不满。当她骤然见到一身正气,八面威风的武松,便惊为天人,暗生情愫,才有了“叔叔若有心,便吃了我这半盏儿残酒。”
偏偏武二郎以正人君子自居,不懂好吃不过饺子,好玩不过......。
仅仅是在被二郎义正辞严拒绝之后,小潘心灰意冷,才会委身西门大官人,可见西门庆只是她的次选。
我们可以推断,在金莲的价值观里,颜值重于人品。在这个坐标系里,三人投影如下:
学过线性代数的同学们知道,把高维向量投射到低维空间的操作是一个线性变换,可以通过向量和矩阵的乘法完成。比如,把 d 维向量 E 投射成 m 维向量 K,只需用 E 乘以一个 d × m 的矩阵 M:
或者说
以 d=4,m=3 为例,我们来把 4 维向量 E 投影成 3 维向量 K:
这里,我们既可以把矩阵 M 看成三个列向量 C1,C2,C3,也可以把它看成四个行向量 R1,R2,R3,R4。
按矩阵乘法的定义,k1 = E⋅C1(E 和 C1 的点积),也就是 e1⋅m11 + e2⋅m21 + e3⋅m31 + e4⋅m41。
同样,k2 = E⋅C2,k3 = E⋅C3。这样便可以计算出 k1,k2,k3 的值。
这种理解当然没错,但是格局小了,只见树木不见森林。
要真正理解 E 是如何被投射成 K 的,我们要把 K 看成 R1,R2,R3,R4 这 4 个行向量的线性组合。其中,Ri 的权重是 ei。
也就是说, K = e1 R1 + e2 R2 + e3 R3 + e4 R4
大家可以试着推一下,这种计算方式和按矩阵乘法定义的计算方式是等价的,不同之处在于这里 R1,... 这几个行向量有清楚的直观意义:
R1 是 4 维向量 I1 = [1 0 0 0] 在目标 3 维空间的投影:R1 = [1 0 0 0] M
R2 是 I2 = [0 1 0 0] 在目标 3 维空间的投影:R2 = [0 1 0 0] M
...
R4 是 I4 = [0 0 0 1] 在目标 3 维空间的投影:R4 = [0 0 0 1] M
所以,把任意 4 维向量 E = [e1 e2 e3 e4] 投射到目标 3 维空间,我们可以这样理解:
把 E 看成 4 个正交(就是彼此垂直)向量的线性组合:E = e1 I1 + e2 I2 + e3 I3 + e4 I4。
E 的投影可以看成这 4 个正交向量的投影的线性组合,权重不变:K = e1 R1 + e2 R2 + e3 R3 + e4 R4。
建议大家慢慢看,把这一段好好消化一下,最好自己用纸笔推导一遍,形成直觉之后会更容易理解自注意力机制的工作原理。
学会线性投影的用处和算法之后,你有没有想过如何用神经网络实现这个算法?
我们知道,在人工神经网络里,神经元之间有权重不一的连接,每个神经元的输出 = f(该神经元全部输入信号的加权和)。这里 f 是这个神经元的激活函数(activation function)。连接的权重是通过学习得到的,而激活函数是事先设计好的。
要用神经网络把 d 维向量 E 投射成 m 维向量 K,只需在 d 个神经元后面用全连接的方式接上一层 m 个神经元,两层神经元之间一共 d × m 个连接,它们的权重就对应于矩阵 M 的 d × m 个元素。第二层的神经元不需要激活函数,或者说它们的激活函数就是 identity (f(x) = x)。
通过调节上图中的连接权重,我们可以使 k1 = e1⋅m11 + e2⋅m21 + e3⋅m31 + e4⋅m41,...。这层神经元的作用是实现线性投影,所以叫做线性投影层(linear projection layer)。
~~发现记号间的相关~~
现在我们来复习一下 transformer 的神经网络架构:
刚刚我们讲解了把输入向量 E 通过线性投影层投射成键向量 K 的详细过程。线性投影层会用同样的方式(但是不同的参数)把 E 投射成查询向量 Q 和值向量 V。
有了和 n 个输入记号对应的键向量 K1,K2,...,Kn 和查询向量 Q1,Q2,...,Qn 之后,我们就可以计算注意力矩阵啦!
简单地说,记号 i 对记号 j 的关注度 = Qi 和 Kj 的相似度。
那么两个向量的相似程度该怎么算呢?
方法有多种。其中一种因为计算方便,在神经网络中最常用,那就是把两个向量做点积(dot product),也就是对应的元素相乘再求和:
[u1 u2 … u_n] ⋅ [v1 v2 … v_n] = u1⋅v1 + u2⋅v2 + … + u_n⋅v_n
所以,要计算记号 i 对记号 j 的关注度,我们只需把 Qi 跟 Kj 做点积就好了。点积运算只对维数相同的向量才有意义,所以查询向量 Q 和键向量 V 的维数必须相同。
在本文开头的小王小张小江相亲的例子里,他们的注意力矩阵就是这么算出来的。
题外话:在这波 AI 热之前,元宇宙曾经热过。实现元宇宙需要借助GPU(graphics processing unit,图形处理器)渲染各种三维场景,GPU 大厂 Nvidia 因此大赚了一笔。在此之前,区块链、数字币大火,对挖矿的需求也曾让 GPU 大卖。巧的是,GPU 也非常适合做神经网络中大量用到的矩阵计算,所以 AI 热潮再一次带动 Nvidia 销量暴增,让连中三次彩票的老板黄仁勋做梦都会笑醒。
乱花渐欲迷人眼,万紫千红总是春。有了注意力矩阵后如何从嘉宾中挑出自己心仪的对象呢?
跟现实中的征婚不同,transformer 的征婚不必只选一个最佳答案,而是可以根据自己对每位嘉宾喜好的程度,做一个加权求和,生造出一个符合自己想象的虚拟嘉宾。
问题是:权重如何选择?
有人可能会想:何不就用注意力矩阵的元素(关注度)做权重?
可惜,注意力矩阵是不适合直接用做权重的。
比如海王去参加一场 100 位嘉宾参加的相亲活动。做为海王,他对多位嘉宾的关注度都很高。如果用关注度做权重把嘉宾们加权求和,海王的虚拟理想嘉宾各项指标就会远超正常人的虚拟嘉宾。这种不对等会严重干扰神经网络的工作。
另外,让权重和关注度成简单的正比关系也不是一个好主意。好钢要用到刀刃上。如果两个成本差不多的项目摆在领导面前,其中一个带来的效益会是另一个的三倍,卓越的领导不会按 3:1 来分配资源,而是会集中投入第一个项目。同样,在设计神经网络的时候,我们也希望让权重向强者倾斜,形成马太效应,这样才能拉开差距突出重点。
所以,transformer 会用“软大”函数(softmax)处理注意力矩阵的每行,达到权重归一化和拉大差距的效果。这个处理的结果叫做得分矩阵(score matrix)。以得分矩阵第 i 行的元素做权重把输入记号的值向量加权求和,就得到了记号 i 的输出向量,也就是它在当前语境下的含义。
软大函数是一个向量到向量的函数:
它的定义是:
不难看出,经软大处理后,向量的每个元素都在 0 到 1 之间,而且所有元素加起来等于 1,符合归一化的要求。因为以 e 为底的指数函数是单调上升的,软大不会改变元素间的大小关系。
transformer 在用 softmax 处理注意力矩阵之前,还先把每个元素都除以键向量维数 m 的平方根。这是因为软大函数在输入元素较大时有加大贫富差异的效果,元素越大效果越明显。为了防止得分矩阵的数值过于集中,我们得把所有元素都缩小一个常数因子再喂给 softmax,不让贫富差异扩大得太过分。
为什么这个因子是键向量维数的平方根?答案可能会让你失望:其实没有什么高深的道理,就是试过了觉得这样效果还不错。
我们以前说过人工神经网络中每个神经元的功能就是把输入信号加权求和后用激活函数变换成输出信号。但是这个软大函数却不能用这种方式实现,所以说以前的说法是简化的,我们在设计神经网络的时候切不可被原始的定义束缚,该突破的时候还是要大胆突破。
我们来对比一下。注意力矩阵是这样的:
经过缩小因子和软大函数处理后,就得到了得分矩阵:
肉眼可见的是,每一行的和都是 1,而且个体间的相对差异被扩大了。
接下来我们看看实现自注意力机制的运算成本。
GPT 一次能处理的记号个数 n(历史窗口)是有上限的。比如,GPT 3.5 的上限是 4096 个记号,GPT 4 是 8192。自注意力矩阵 A 的大小是 n 的平方,如果记号数翻一番,A 的参数个数就得翻两番,所以给历史窗口扩容并不容易。
不过,只要是在历史窗口内的记号,GPT 都一视同仁,处理起来没有远近亲疏的区别。一个记号如果要关注间隔 4000 个记号之外的记号没有任何问题,不会比关注隔壁的记号困难一丝一毫。关注的能力不会随着距离衰减,所以 transformer 能轻松处理文字中的远程相关。
还有,注意力矩阵中每个元素的计算都是独立的,不存在依赖性,完全可以并行计算。
你还记得前几年的“量子波动速读法”吗?据称,只要掌握了这种方法,就能“直接以心灵感应的方式高速获取信息”,10 分钟不到就能读完一本 10 万字的书,还能准确复述 80% 以上的内容。重点来了:要学会这一神技,只需要交 5 万块钱上个培训班。
“傻子被动速读法”自然是骗子的无稽之谈。不过,虽然人类做不到,chatGPT 却真的练成了这种神功。transformer 一抬眼,全部输入记号就尽收眼底,开动算力同时计算任两个记号间的关注度。这比 RNN (循环神经网络)的串行计算效率高出万分。所以说谷歌发明的 transformer 引爆了这次大语言模型技术革命。
~~实例分析~~
下面我们举一个具体的例子来展示自注意力机制工作的全部过程。
“俺最美”这句话有三个字,姑且认为就是三个记号(tokens)。首先我们用一层神经网络把这些记号转化为它们各自的嵌入向量和位置编码,然后逐对相加得到三个输入向量。
然后经过矩阵乘法降维,得到三个查询向量、三个键向量、还有三个值向量。
因为“俺”是一个人物代词,大模型通过学习已经知道它可能会比较关注形容词,而对副词不是那么关注。也就是说经机器学习得到的“俺”的查询向量会离形容词的键向量比较近,离副词的键向量比较远。
“最”的键向量表明它是一个副词,所以“俺”对“最”的关注度比较低。
“美”的键向量包含了这样的信息:它是形容词,而且是可以用在人身上的形容词。所以,“俺”的查询向量和“美”的键向量就会比较接近,前者对后者的关注度就较高。
机器经过学习知道副词对形容词关注度高,所以神经网络的计算结果会表明“最”最关注的是“美”,而不是“俺”。
类似地,我们可以算出“美”对“俺”的关注度比较高。
综合起来,我们就可以算出这三个输入记号的注意力矩阵:
然后我们再对这个矩阵的元素做缩放 + softmax 操作,得到得分矩阵。用得分矩阵的元素做权重对三个值向量加权求和,得到三个符号的输出向量。这些输出向量又会被送到下一级 transformer,抽取更高级的特征。
~~多头注意力~~
在实践中我们发现这种自注意力机制还是有很大的不足:每位嘉宾只允许关注一位虚拟嘉宾,不能吃着碗里的想着锅里的。在自然语言中,有时候一个字和好几个字都有很强的关联,更不用说很多时候一个字是拆成几个记号处理的,所以一个记号往往需要同时关注多个记号。
比如在“老王给老张送了一顶绿油油的帽子,他一气之下赏了他一个大嘴巴”的例子中,我们需要找出第一个“他”和“老张”的强相关,也就是说这个“他”既要跟第二个“老”强相关,也要跟“张”强相关。
在谐音哏和双关语笑话中,我们甚至故意让语言有歧义,同一个字会根据不同的解读方式和不同的字发生强相关。比如(非笑果旗下)脱口秀演员周奇墨说过的段子:士兵长跑送信,累得喝口水就死了,前方打的是口水仗吗?
为了让模型看得懂这样的哏,transformer 把前面讲的简单自注意力机制改进了一下,变有了多头(multi-head)注意力机制:
把每个 transformer 中的注意力机制复制多份,每份叫一个头(head)。每个注意力头负责关注一个不同层面上的意思。比如一个头看见“口水”是一口水的意思,另一个头看到的是唾液的意思。这样可以更好地理解语言的复杂含义。
transformer 本没有多头,段子看多了,便有了多头。
不过,这么做的问题是运算量会急剧上升。好在我们仍然可以祭出降维化简大法,让每个头关注不同的输入向量子空间,降低计算量。
比如,如果输入向量是 768 维,我们可以用 12 个注意力头,每个头处理768/12 = 64 维的数据。具体地说,先通过一个矩阵变换把 768 维输入投射到 12 个不同的 64 维子空间,然后对每个 64 维向量分别考察。最后把这 12 个注意力头的输出拼接到一起得到 transformer 的最后输出。
多头注意力机制的架构是这样的(建议点击放大后横屏观看):
这样相当于每位嘉宾可以娶回家不只一个对象,而是金陵 12 钗,幸福指数陡增。
~~~~
经过细致的理论学习,大家都累了吧。下次我们不妨动手写 code 实践一下,比如用向量数据库实现长期记忆,弥补 chatGPT 历史窗口有限的缺陷。
I’ll be back.
(除图表、照片外,本文插图由 Midjourney 生成。)
~~~~~~~~~~
猜你会喜欢:
谷歌对微软:代码管理工具哪家强?- 要集中还是要分布
谷歌新语言 Carbon 能干翻 C++ 吗?- 深入浅出分析 Carbon
后 C++ 演义(第一回、第二回) - 起底 C++ 发明人比雅尼
后 C++ 演义(第三回) - C++ 的最新发展
程序员护发秘籍 - 掌握这些工作技巧,包你不脱发
程序员的核心技能 - 以脱口秀的方式讲解程序员最重要的技能
如何做出保鲜十年的软件 - 老码农冒死披露行业内幕系列
dongbei 语言满月记事 - 一种基于东北方言的娱乐式程序设计语言
~~~~~~~~~~
关注老万故事会公众号:
本公众号不开赞赏不放广告。如果喜欢这篇文章,欢迎点赞、在看、转发。谢谢大家🙏